﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using MyMath = DDD.Helper.MyMath;

namespace DDD.Core {

    /// <summary>
    /// アニメーション トラック クラス
    /// </summary>
    /// <remarks>
    /// <see cref="AnimationTrack"/> は一連のキーフレーム データを保存しアニメーションの元データとなります。
    /// トラックは <see cref="AnimationClip"/> によって補完・制御されます。
    /// アニメーション クリップにトラックを追加するには <see cref="AnimationClip.AddTracker()"/> を使用します。
    /// クリップは再生されるとトラックのキーフレーム データから
    /// 指定ポジションの値を計算し、ターゲットのプロパティを変更します。
    /// 1つのトラックが1つのプロパティを書き換えます。
    /// またトラックには指定ポジションで呼び出されるアニメーション イベントを定義する事が可能です｡
    /// <para>
    /// <h3>アニメーション イベント</h3>
    /// トラックには指定ポジションで任意のハンドラー メソッドを呼び出す機能があり、これをアニメーション イベントと呼びます。
    /// イベントは、(1) イベントが起きるポジション、(2) そのとき呼び出されるハンドラー（メソッド）、(3)ハンドラーに渡される引数の3つから構成されます。
    /// <see cref="AnimationPlayer"/> の <see cref="AnimationPlayer.Raise()"/> メソッドを呼ぶと、指定のワールド時間無いの
    /// イベント ハンドラーがすべて呼び出されます。
    /// イベントの詳細については、<see cref="AnimationEvent"/> 構造体の解説を参照してください。
    /// イベント ハンドラーの詳細については、<see cref="AnimationEvnetHandler"/> ハンドラーの解説を参照してください。
    /// </para>
    /// </remarks>
    /// <seealso cref="AnimationClip"/>
    /// <seealso cref="AnimationPlayer"/>
    /// <seealso cref="AnimationEvent"/>
    /// <seealso cref="AnimationEventHandler"/>
    public class AnimationTrack : Object {

        #region Field
        Keyframe[] keyframes;
        int componentCount;
        InterpolationType interpolation;
        int duration;
        ValidRange validRange;
        LoopMode loopMode;
        List<AnimationEvent> events;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// 初期値は、
        /// <list type="bullet">
        ///   <item><see cref="LoopMode"/> = <see cref="LoopMode.Single"/></item>
        ///   <item><see cref="Duration"/> = 0</item>
        ///   <item><see cref="ValidRange"/> = [0,<paramref name="numKeyframes"/>]</item>
        ///   <item><see cref="EventCount"/> = 0</item>
        /// </list>
        /// です。
        /// すべてのキーフレームは有効で、アニメーション イベントは何も登録されていません｡
        /// 全てのキーフレームは下記の初期値で初期化されます｡
        /// <list type="bullet">
        ///   <item><see cref="Keyframe.Position"/> = 0</item>
        ///   <item><see cref="Keyframe.Value"/> = 全て0</item>
        /// </list>
        /// 補完タイプ <see cref="InterpolationType.Slerp"/> はクォータニオン専用です。
        /// <note type="implement">
        /// キーフレーム データはすべて <c>float[]</c> で保存します。
        /// <c>byte</c> 型や <c>short</c> 型で保存したいという要求は理解しています。
        /// そのうち実装したいと思います。
        /// </note>
        /// </remarks>
        /// <param name="numKeyframes">キーフレーム数</param>
        /// <param name="numComponents">コンポーネント数</param>
        /// <exception cref="ArgumentException"><paramref name="numKeyframes"/> &lt; </exception>
        /// <exception cref="ArgumentException"><paramref name="numCompoents"/> &lt; 0 または <paramref name="numComponents"/> &gt; 16</exception>
        /// <exception cref="ArgumentException"><paramref name="type"/> = <see cref="InterpolationType.Slerp"/> かつ <paramref name="numComponents"/> != 4</exception>
        /// <exception cref="NotImplementedException"><paramref name="type"/> = <see cref="InterpolationType.Slerp"/> (まだ実装していません）</exception>
        /// <param name="type">補間タイプ</param>
        public AnimationTrack (int numKeyframes, int numComponents, InterpolationType type) {
            if (numKeyframes <= 0) {
                throw new ArgumentException ("Number of keyframes is invalid");
            }
            if (numComponents <= 0 || numComponents > 16) {
                throw new ArgumentException ("Number of components is invalid");
            }
            if (type == InterpolationType.Slerp && numComponents != 4) {
                throw new ArgumentException ("Number of components must be 4 when Slerp");
            }
            if (type == InterpolationType.SQuad) {
                throw new NotImplementedException ("Sorry, use slerp instead");
            }

            this.componentCount = numComponents;
            this.interpolation = type;
            this.duration = 0;
            this.validRange = new ValidRange (0, numKeyframes - 1);
            this.loopMode = LoopMode.Single;
            this.events = new List<AnimationEvent> ();

            this.keyframes = new Keyframe[numKeyframes];
            for (var i = 0; i < numKeyframes; i++) {
                this.keyframes[i] = new Keyframe (0, new float[numComponents]);
            }
        }
        #endregion

        #region Property
        /// <summary>
        /// キーフレーム数
        /// </summary>
        /// <remarks>
        /// このトラックが持つキーフレームの個数です。
        /// </remarks>
        public int KeyframeCount {
            get { return keyframes.Length; }
        }

        /// <summary>
        /// コンポーネント数
        /// </summary>
        /// <remarks>
        /// 1つのキーフレームが持つコンポーネントの数です。
        /// [1,16] の値が返ります。
        /// </remarks>
        public int ComponentCount {
            get { return componentCount; }
        }

        /// <summary>
        /// イベント数
        /// </summary>
        /// <remarks>
        /// このトラックに登録されたアニメーション イベントの総数です。
        /// </remarks>
        public int EventCount {
            get { return events.Count (); }
        }

        /// <summary>
        /// 補間タイプ
        /// </summary>
        /// <remarks>
        /// キーフレームを補完するさいに使用される補完方式です。
        /// <see cref="InterpolationType"/> の中から1つが返ります。
        /// </remarks>
        /// <seealso cref="InterpolationType"/>
        public InterpolationType InterpolationType {
            get { return interpolation; }
        }

        /// <summary>
        /// 1回の再生時間
        /// </summary>
        /// <remarks>
        /// このトラックを最初から最後まで再生した時の（ローカルの）時間です。
        /// この再生時間を <see cref="AnimationClip.Speed"/> で割ったものがワールド時間での再生時間に相当します。
        /// </remarks>
        public int Duration {
            get { return duration; }
        }

        /// <summary>
        /// 有効キーフレーム範囲 
        /// </summary>
        /// <remarks>
        /// このトラックが持つキーフレーム シーケンスの中で、現在有効にされている範囲をです。
        /// [<see cref="ValidRange.First"/>, <see cref="ValidRange.Last"/>] が有効です
        /// （First を含み Last も含む）。
        /// </remarks>
        public ValidRange ValidRange {
            get { return validRange; }
        }

        /// <summary>
        /// 繰り返しモード
        /// </summary>
        /// <remarks>
        /// このトラックの持つ繰り返しモードです。
        /// </remarks>
        /// <seealso cref="LoopMode"/>
        public LoopMode LoopMode {
            get { return loopMode; }
        }

        /// <summary>
        /// すべてのキーフレームを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// このトラックが持つすべてのキーフレームを列挙します。
        /// <note type="important">
        /// 列挙子はコピーなので書き換えても、原則本体側には反映されない｡
        /// 実際には <see cref="Keyframe.Value"/> は配列なので書き換わるけど、やるのは反則｡
        /// </note>
        /// </remarks>
        public IEnumerable<Keyframe> Keyframes {
            get { return keyframes; }
        }

        /// <summary>
        /// すべてのイベントを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// このトラックに登録されたイベントをすべて列挙します。
        /// </remarks>
        /// <see cref="AnimationEvent"/>
        public IEnumerable<AnimationEvent> Events {
            get { return events; }
        }
        #endregion

        #region Method
        /// <summary>
        /// キーフレームをサンプリングします
        /// </summary>
        /// <remarks>
        /// このトラックが持つキーフレーム シーケンスから、
        /// 指定のポジション <see cref="position"/> の値を計算して <paramref cref="value"/> に書き込みます｡
        /// <see cref="position"/> はワールド時間ではなくローカルのポジションです。
        /// [0, <see cref="Duration"/>] に限定されている必要はありません。
        /// </remarks>
        /// <param name="position">ポジション</param>
        /// <param name="value">サンプリング結果を書き込む配列</param>
        /// <exception cref="ArgumentNullException"><paramref name="value"/> = <c>null</c></exception>
        /// <exception cref="ArgumentException"><paramref name="value"/>.<see cref="Array.Length"/> &lt; <see cref="ComponentCount"/></exception>
        /// <exception cref="InvalidOperationException"><paramref name="Duration"/> = 0</exception>
        /// <exception cref="InvalidOperationException">キーフレーム シーケンスが単調増加ではない</exception>
        public void Sample (float position, float[] value) {
            if (value == null) {
                throw new ArgumentNullException ("Value is null");
            }
            if (value.Length < componentCount) {
                throw new ArgumentException ("Value length is less than ComponentCount");
            }
            if (duration == 0) {
                throw new InvalidOperationException ("Duration is 0");
            }
            if (!IsMonotonicallyIncreasing ()) {
                throw new InvalidOperationException ("Keyframes are not monotonically increasing");
            }

            if (validRange.Count == 1) {
                SampleSpecialCase (position, value);
                return;
            }

            switch (loopMode) {
                case LoopMode.Single: SampleOnce (position, value); return;
                case LoopMode.Loop: SampleLoop (position, value); return;
                case LoopMode.AdditiveLoop: SampleAdditiveLoop (position, value); return;
                default: throw new NotImplementedException ("Sorry");
            }

        }

        /// <summary>
        /// キーフレームが単調増加である事の判定
        /// </summary>
        /// <remarks>
        /// キーフレーム シーケンスがはインデックスが小さい方から順にポジションが昇順で整列していないと計算ができません。
        /// ただし <see cref="ValidRange"/> の範囲内で。
        /// </remarks>
        /// <returns>単調増加ならtrue, そうでなければfalse</returns>
        private bool IsMonotonicallyIncreasing () {
            var first = validRange.First;
            var last = validRange.Last;
            for (var i = first; i < last; i++) {
                if (keyframes[i].Position > keyframes[i + 1].Position) {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// サンプリング(特殊なケース)
        /// </summary>
        /// <remarks>
        /// キーフレームが1つの時は他とかなり違うので独立させた｡
        /// </remarks>
        /// <param name="position">ポジション</param>
        /// <param name="value">結果を書き込む配列</param>
        private void SampleSpecialCase (float position, float[] value) {
            for (var i = 0; i < componentCount; i++) {
                value[i] = keyframes[validRange.First].Value[i];
            }
        }

        /// <summary>
        /// 指定のポジションを[0,<see cref="Duration"/>]の範囲に折りたたむ
        /// </summary>
        /// <remarks>
        /// 指定のポジションが[0,<see cref="Duration"/>]の範囲（0と <see cref="Duration"/> を含む）になるように
        /// <see cref="Duration"/>ずつ移動させます｡
        /// </remarks>
        /// <param name="position">ポジション(in,out)</param>
        /// <returns>折りたたんだ回数</returns>
        private int ShiftPosition (ref float position) {
            var n = 0;
            while (position < 0 || position > duration) {
                if (position < 0) {
                    position += duration;
                    n = n - 1;
                }
                if (position > duration) {
                    position -= duration;
                    n = n + 1;
                }
            }
            return n;
        }

        /// <summary>
        /// ループモードのサンプリング
        /// </summary>
        /// <remarks>
        /// <see cref="LoopMode"/> が <see cref="LoopMode.Loop"/> の時のサンプリング メソッド。
        /// </remarks>
        /// <param name="position">ポジション</param>
        /// <param name="value">結果を書き込む配列</param>
        private void SampleLoop (float position, float[] value) {
            var first = validRange.First;
            var last = validRange.Last;
            ShiftPosition (ref position);
            var l = Array.FindLastIndex (keyframes, last, last - first + 1, x => x.Position <= position);
            var r = Array.FindIndex (keyframes, first, last - first + 1, x => x.Position > position);

            var f = new Keyframe[4];
            if (l == -1) {
                f[0] = new Keyframe (keyframes[last - 1].Position - duration, keyframes[last - 1].Value);
                f[1] = new Keyframe (keyframes[last].Position - duration, keyframes[last].Value);
            }
            else if (l == first) {
                f[0] = new Keyframe (keyframes[last].Position - duration, keyframes[last].Value);
                f[1] = new Keyframe (keyframes[l].Position, keyframes[l].Value);
            }
            else {
                f[0] = new Keyframe (keyframes[l - 1].Position, keyframes[l - 1].Value);
                f[1] = new Keyframe (keyframes[l].Position, keyframes[l].Value);
            }
            if (r == -1) {
                f[2] = new Keyframe (keyframes[first].Position + duration, keyframes[first].Value);
                f[3] = new Keyframe (keyframes[first + 1].Position + duration, keyframes[first + 1].Value);
            }
            else if (r == last) {
                f[2] = new Keyframe (keyframes[r].Position, keyframes[r].Value);
                f[3] = new Keyframe (keyframes[first].Position + duration, keyframes[first].Value);
            }
            else {
                f[2] = new Keyframe (keyframes[r].Position, keyframes[r].Value);
                f[3] = new Keyframe (keyframes[r + 1].Position, keyframes[r + 1].Value);
            }
            float s = (position - f[1].Position) / (f[2].Position - f[1].Position);


            switch (interpolation) {
                case InterpolationType.Step: Interpolate (s, f[0], f[1], f[2], f[3], value); break;
                case InterpolationType.Linear: Interpolate (s, f[0], f[1], f[2], f[3], value); break;
                case InterpolationType.Slerp: Interpolate (s, f[0], f[1], f[2], f[3], value); break;
                case InterpolationType.Spline: Interpolate (s, f[0], f[1], f[2], f[3], value); break;
                default: throw new NotImplementedException ("Sorry");
            }

        }

        /// <summary>
        /// 加算ループモードのサンプリング
        /// </summary>
        /// <remarks>
        /// <see cref="LoopMode"/> が <see cref="LoopMode.AdditiveLoop"/> の時のサンプリング メソッド。
        /// </remarks>
        /// <param name="position">ポジション</param>
        /// <param name="value">結果を書き込む配列</param>
        private void SampleAdditiveLoop (float position, float[] value) {
            var first = validRange.First;
            var last = validRange.Last;
            var n = ShiftPosition (ref position);
            var l = Array.FindLastIndex (keyframes, last, last - first + 1, x => x.Position <= position);
            var r = Array.FindIndex (keyframes, first, last - first + 1, x => x.Position > position);
            var offsetValue = MyMath.Subtract (keyframes[last].Value, keyframes[first].Value);


            var f = new Keyframe[4];
            if (l == -1) {
                f[0] = new Keyframe (keyframes[last - 1].Position - duration, MyMath.Subtract (keyframes[last - 1].Value, offsetValue));
                f[1] = new Keyframe (keyframes[last].Position - duration, MyMath.Subtract (keyframes[last].Value, offsetValue));
            }
            else if (l == first) {
                f[0] = new Keyframe (keyframes[last].Position - duration, MyMath.Subtract (keyframes[last].Value, offsetValue));
                f[1] = new Keyframe (keyframes[l].Position, keyframes[l].Value);
            }
            else {
                f[0] = new Keyframe (keyframes[l - 1].Position, keyframes[l - 1].Value);
                f[1] = new Keyframe (keyframes[l].Position, keyframes[l].Value);
            }
            if (r == -1) {
                f[2] = new Keyframe (keyframes[first].Position + duration, MyMath.Add (keyframes[first].Value, offsetValue));
                f[3] = new Keyframe (keyframes[first + 1].Position + duration, MyMath.Add (keyframes[first + 1].Value, offsetValue));
            }
            else if (r == last) {
                f[2] = new Keyframe (keyframes[r].Position, keyframes[r].Value);
                f[3] = new Keyframe (keyframes[first].Position + duration, MyMath.Add (keyframes[first].Value, offsetValue));
            }
            else {
                f[2] = new Keyframe (keyframes[r].Position, keyframes[r].Value);
                f[3] = new Keyframe (keyframes[r + 1].Position, keyframes[r + 1].Value);
            }
            for (var i = 0; i < 4; i++) {
                f[i] = new Keyframe (f[i].Position, MyMath.AddMultiply (f[i].Value, offsetValue, n));
            }
            float s = (position - f[1].Position) / (f[2].Position - f[1].Position);

            switch (interpolation) {
                case InterpolationType.Step: Interpolate (s, f[0], f[1], f[2], f[3], value); break;
                case InterpolationType.Linear: Interpolate (s, f[0], f[1], f[2], f[3], value); break;
                case InterpolationType.Slerp: Interpolate (s, f[0], f[1], f[2], f[3], value); break;
                case InterpolationType.Spline: Interpolate (s, f[0], f[1], f[2], f[3], value); break;
                default: throw new NotImplementedException ("Sorry");
            }

        }

        /// <summary>
        /// 一回再生モードのサンプリング
        /// </summary>
        /// <remarks>
        /// <see cref="LoopMode"/> が <see cref="LoopMode.Sigle"/> の時のサンプリング メソッド。
        /// </remarks>

        /// <param name="position">ポジション</param>
        /// <param name="value">結果を書き込む配列</param>
        private void SampleOnce (float position, float[] value) {
            var first = validRange.First;
            var last = validRange.Last;
            var l = Array.FindLastIndex (keyframes, last, last - first + 1, x => x.Position <= position);
            var r = Array.FindIndex (keyframes, first, last - first + 1, x => x.Position > position);

            var f = new Keyframe[4];
            if (l == -1) {
                f[0] = new Keyframe (keyframes[first].Position - duration, keyframes[first].Value);
                f[1] = new Keyframe (keyframes[first].Position - duration, keyframes[first].Value);
            }
            else if (l == first) {
                f[0] = new Keyframe (keyframes[first].Position - duration, keyframes[first].Value);
                f[1] = new Keyframe (keyframes[l].Position, keyframes[l].Value);
            }
            else {
                f[0] = new Keyframe (keyframes[l - 1].Position, keyframes[l - 1].Value);
                f[1] = new Keyframe (keyframes[l].Position, keyframes[l].Value);
            }
            if (r == -1) {
                f[2] = new Keyframe (keyframes[last].Position + duration, keyframes[last].Value);
                f[3] = new Keyframe (keyframes[last].Position + duration, keyframes[last].Value);
            }
            else if (r == last) {
                f[2] = new Keyframe (keyframes[r].Position, keyframes[r].Value);
                f[3] = new Keyframe (keyframes[last].Position + duration, keyframes[last].Value);
            }
            else {
                f[2] = new Keyframe (keyframes[r].Position, keyframes[r].Value);
                f[3] = new Keyframe (keyframes[r + 1].Position, keyframes[r + 1].Value);
            }
            float s = (position - f[1].Position) / (f[2].Position - f[1].Position);

            switch (interpolation) {
                case InterpolationType.Step: Interpolate (s, f[0], f[1], f[2], f[3], value); break;
                case InterpolationType.Linear: Interpolate (s, f[0], f[1], f[2], f[3], value); break;
                case InterpolationType.Slerp: Interpolate (s, f[0], f[1], f[2], f[3], value); break;
                case InterpolationType.Spline: Interpolate (s, f[0], f[1], f[2], f[3], value); break;
                default: throw new NotImplementedException ("Sorry");
            }

        }

        /// <summary>
        /// キーフレーム(x4)の補間計算
        /// </summary>
        /// <remarks>
        /// キーフレーム4点（<paramref name="frame0"/>, <paramref name="frame1"/>, <paramref name="frame2"/>, <paramref name="frame3"/>）から
        /// 指定の補完パラメーター <paramref name="s"/> [0,1] の補完値を計算します。
        /// <paramref name="s"/> = 0 の時 <paramref name="frame1"/>が使用され､
        /// <paramref name="s"/> = 1 の時 <paramref name="frame2"/>が使用されます。
        /// <paramref name="frame0"/> と <paramref name="frame2"/> は接線の傾きを計算するのに使用されます。
        /// この2つは <see cref="InterpolationTypte"/> によっては使用されません。
        /// </remarks>
        /// <param name="s">補間パラメーター[0,1]</param>
        /// <param name="frame0">フレーム0</param>
        /// <param name="frame1">フレーム1(メイン)</param>
        /// <param name="frame2">フレーム2(メイン)</param>
        /// <param name="frame3">フレーム0</param>
        /// <param name="value">結果を書き込む配列</param>
        private void Interpolate (float s, Keyframe frame0, Keyframe frame1, Keyframe frame2, Keyframe frame3, float[] value) {
            if (s < 0 || s > 1) {
                throw new ArgumentException ("S is invalid");
            }
            var t0 = frame0.Position;
            var t1 = frame1.Position;
            var t2 = frame2.Position;
            var t3 = frame3.Position;
            var v0 = frame0.Value;
            var v1 = frame1.Value;
            var v2 = frame2.Value;
            var v3 = frame3.Value;

            switch (interpolation) {
                case InterpolationType.Step: {
                        for (var i = 0; i < componentCount; i++) {
                            value[i] = v1[i];
                        }
                        return;
                    }
                case InterpolationType.Linear: {
                        for (var i = 0; i < componentCount; i++) {
                            value[i] = v1[i] * (1 - s) + v2[i] * s;
                        }
                        return;
                    }
                case InterpolationType.Spline: {
                        for (var i = 0; i < componentCount; i++) {
                            var F1 = 2 * (t2 - t1) / (t2 - t0);
                            var F2 = 2 * (t2 - t1) / (t3 - t1);
                            var T1 = F1 * (v2[i] - v0[i]) / 2.0f;
                            var T2 = F2 * (v3[i] - v1[i]) / 2.0f;
                            var a = 2 * v1[i] - 2 * v2[i] + T1 + T2;
                            var b = -3 * v1[i] + 3 * v2[i] - 2 * T1 - T2;
                            var c = T1;
                            var d = v1[i];
                            value[i] = s * s * s * a + s * s * b + s * c + d;
                        }
                        return;
                    }
                case InterpolationType.Slerp: {
                        var q1 = Quaternion.Set (v1);
                        var q2 = Quaternion.Set (v2);
                        var q = Quaternion.Slerp (s, q1, q2);
                        for (var i = 0; i < componentCount; i++) {
                            value[i] = q[i];
                        }
                        return;
                    }
                case InterpolationType.SQuad: {
                        throw new NotImplementedException ("Sorry, use Slerp instead");
                    }
                default: throw new NotImplementedException ("Sorry");
            }
        }

        /// <summary>
        /// キーフレームの取得
        /// </summary>
        /// <remarks>
        /// 指定のインデックス <paramref name="index"/> のキーフレームを取得します。
        /// 戻り値のキーフレーム構造体は"コピー"です｡ 値を書き換えても本体側に保存されているデータは変わりません。
        /// <note type="implement">
        /// 実のところ <see cref="Keyframe.Value"/> は配列なので、配列の中身を書き換えると本体側も変わる。
        /// パフォーマンスの問題があるので配列の複製を作って渡す気は無い。
        /// やったら駄目よ。
        /// </note>
        /// </remarks>
        /// <param name="index">キーフレーム番号</param>
        /// <exception cref="IndexOutOfRangeException"><paramref name="index"/> &lt; 0 または <paramref name="index"/> &gt; <see cref="KeyframeCount"/> - 1</exception>
        /// <returns>キーフレームのコピー</returns>
        public Keyframe GetKeyframe (int index) {
            if (index < 0 || index > KeyframeCount - 1) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            return keyframes[index];
        }

        /// <summary>
        /// キーフレームの更新
        /// </summary>
        /// <remarks>
        /// 指定のインデックス <paramref name="index"/> のキーフレームの値を変更します。
        /// 値は内部にコピーされるので、このメソッドから戻った後は <paramref name="value"/> は解放してかまいません。
        /// すべてのキーフレームのポジションは単調増加である必要がありますが、このメソッドではチェックしません（できません）。
        /// このトラックをサンプリングする時に例外が発生する場合があります。
        /// </remarks>
        /// <param name="index">キーフレーム番号</param>
        /// <param name="position">ポジション</param>
        /// <param name="value">値</param>
        /// <exception cref="IndexOutOfRangeException"><paramref name="index"/> &lt; 0 または <paramref name="index"/> &gt; <see cref="KeyframeCount"/> - 1</exception>
        /// <exception cref="ArgumentNullException"><paramref name="value"/> = <c>null</c></exception>
        /// <exception cref="ArgumentException"><paramref name="value"/>.<see cref="Array.Length"/> &lt; <see cref="ComponentCount"/></exception>
        public void SetKeyframe (int index, int position, float[] value) {
            if (index < 0 || index > KeyframeCount - 1) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            if (value == null) {
                throw new ArgumentNullException ("Value is null");
            }
            if (value.Length < componentCount) {
                throw new ArgumentException ("Value is less than ComponentCount");
            }
            this.keyframes[index] = new Keyframe (position, value);
        }

        /// <summary>
        /// 再生時間の設定
        /// </summary>
        /// <remarks>
        /// このトラックを1回再生した時の再生時間を設定します。
        /// <paramref cref="duration"/> は最終キーフレームのポジションと同じか長い必要があります。
        /// それより短い場合トラックのサンプリング時に例外が発生する場合があります。
        /// <see cref="Duration"/>はこのトラックの基本的な長さであり、アニメーションはこの
        /// <see cref="Duration"/> を元に繰り返されます｡
        /// </remarks>
        /// <param name="duration">再生時間</param>
        /// <exception cref="ArgumentException"><paramref name="duration"/> &lt;= 0</exception>
        public void SetDuration (int duration) {
            if (duration <= 0) {
                throw new ArgumentException ("Duratio is invalid");
            }
            this.duration = duration;
        }

        /// <summary>
        /// 有効キーフレームの設定
        /// </summary>
        /// <remarks>
        /// このトラックが持つキーフレームの合計 <see cref="KeyframeCount"/> の中から、一部の範囲だけ取り出して使用する事ができます。
        /// この範囲を有効キーフレーム <see cref="ValidRange"/> といい、
        /// [<paramref name="first"/>, <paramref name="last"/>] で指定します(<paramref name="first"/> と <paramref name="last"/> はともに有効）。
        /// 一連のキーフレーム データのうち<see cref="ValidRange"/>の範囲のキーフレームのみが使用されます｡
        /// 必ず <paramref name="first"/> &lt; <paramref name="last"/> である必要があります。
        /// </remarks>
        /// <param name="first">最初の有効インデックス(このフレームを含む)</param>
        /// <param name="last">最後有効インデックス(このフレームを含む)</param>
        /// <exception cref="ArgumentException"><paramref name="first"/> &lt; 0 または <paramref name="last"/> &lt; 0</exception>
        /// <exception cref="ArgumentException"><paramref name="first"/> &gt; <paramref name="last"/></exception>
        public void SetValidRange (int first, int last) {
            if (first < 0 || last < 0) {
                throw new ArgumentException ("ValidRange is invalid");
            }
            if (first > last) {
                throw new ArgumentException ("First and last index is invalid");
            }
            this.validRange = new ValidRange (first, last);
        }

        /// <summary>
        /// 繰り返しモードの設定
        /// </summary>
        /// <remarks>
        /// 新しい <see cref=" LoopMode"/> を設定します。
        /// 繰り返しモードの詳細については、<see cref="AnimationTrack"/> の解説を参照してください。
        /// </remarks>
        /// <param name="loop">繰り返しモード</param>
        public void SetLoopMode (LoopMode loop) {
            this.loopMode = loop;
        }

        /// <summary>
        /// イベントの登録
        /// </summary>
        /// <remarks>
        /// このトラックにアニメーション イベントを登録します。
        /// 登録されたイベントは指定時刻に <see cref="AnimationPlayer.Raise()"/> によって、ハンドラーが呼び出されます。
        /// イベントの詳細については、<see cref="AnimationTrack"/> クラスの解説を参照してください。
        /// イベントの呼び出し条件の詳細については、<see cref="AnimationPlayer"/> の解説を参照してください。
        /// 同じポジション、同じハンドラー、同じイベント引数を登録可能です。
        /// 同時刻にセットしたポジションの呼び出し順は定義されていません。
        /// </remarks>
        /// <param name="position">イベント ポジション</param>
        /// <param name="handler">イベント ハンドラー</param>
        /// <param name="args">ハンドラーに渡される引数(<c>null</c> 可)</param>
        /// <exception cref="ArgumentException"><paramref name="position"/> &lt; 0</exception>
        /// <exception cref="ArgumentNullException"><paramref name="handler"/> = <c>null</c></exception>
        /// <see cref="AnimationPlayer"/>
        /// <see cref="AnimationEventHandler"/>
        /// <see cref="EventArgs"/>
        public void AddEvent (int position, AnimationEventHandler handler, EventArgs args) {
            if (position < 0) {
                throw new ArgumentException ("Position is out of duration");
            }
            if (handler == null) {
                throw new ArgumentNullException ("Handler is null");
            }
            this.events.Add (new AnimationEvent (position, handler, args));
        }

        /// <summary>
        /// イベントの削除
        /// </summary>
        /// <remarks>
        /// 登録済みのイベントから指定のハンドラー <see cref="handler"/> を持つ者をすべて削除します。
        /// 同一のハンドラーが複数回登録されていた場合、すべて削除します｡
        /// <see cref="handler"/> が <c>null</c> の場合単にこれを無視します。
        /// </remarks>
        /// <param name="handler">削除したいハンドラー</param>
        /// <returns>削除したハンドラーの個数</returns>
        public int RemoveEvent (AnimationEventHandler handler) {
            return this.events.RemoveAll (x => x.Handler == handler);
        }
        
        /// <summary>
        /// イベントの取得
        /// </summary>
        /// <remarks>
        /// 登録済みのアニメーション イベントを取得します。
        /// </remarks>
        /// <param name="index">イベント番号</param>
        /// <exception cref="IndexOutOfRangeException"><paramref name="index"/> &lt; 0 または <paramref name="index"/> &gt;= <see cref="EventCount"/></exception>
        /// <returns>イベント</returns>
        public AnimationEvent GetEvent (int index) {
            if (index < 0 || index >= events.Count) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            return events[index];
        }

        /// <summary>
        /// 指定区間内のイベントの取得
        /// </summary>
        /// <remarks>
        /// このトラックに登録されたイベントで指定区間[<paramref name="start"/>,<paramref name="end"/>) 内に起きるものを列挙します。
        /// 指定する区間の長さ(<paramref name="end"/> - <paramref name="start"/>) は <see cref="Duration"/> 以下である必要があります。
        /// </remarks>
        /// <param name="start">開始ポジション(この時刻を含む)</param>
        /// <param name="end">終了ポジション(この時刻を含まない)</param>
        /// <exception cref="ArgumentException"><paramref name="start"/> &gt; <paramref name="end"/></exception>
        /// <exception cref="ArgumentException"><paramref name="end"/> - <paramref name="start"/> &gt;= <see cref="Duration"/></exception>
        /// <exception cref="InvalidOperationException"><see cref="Duration"/> = 0</exception>
        /// <exception cref="InvalidOperationException"><see cref="Duration"/> &lt; <c>イベントの最大ポジション</c></exception>
        /// <returns>イベント（複数）</returns>
        public IEnumerable<AnimationEvent> GetRaisingEvents (float start, float end) {
            if (start > end) {
                throw new ArgumentException ("Start and end is invalid");
            }
            if (end - start >= duration) {
                throw new ArgumentException ("(End - start) is greater than duration");
            }
            if (duration == 0) {
                throw new InvalidOperationException ("Duration is 0");
            }
            if (duration < events.Max (x => x.Position)) {
                throw new InvalidOperationException ("Invalid events, position is greater than duration");
            }

            if (loopMode == LoopMode.Single) {
                return from ev in events
                       where (ev.Position >= start && ev.Position < end) || (ev.Position == start && ev.Position == end)
                       select ev;
            }
            ShiftPosition (ref start);
            ShiftPosition (ref end);
            if (start <= end) {
                return from ev in events
                       where (ev.Position >= start && ev.Position < end) || (ev.Position == start && ev.Position == end)
                       select ev;
            }
            else {
                return from ev in events
                       where (ev.Position >= 0 && ev.Position < end) || (ev.Position >= start && ev.Position <= Duration)
                       select ev;
            }
        }

        /// <inheritdoc/>
        public override string ToString () {
            return string.Format ("Track:");
        }

        #endregion

    }

}
